



// 实现  Skinning mesh
// 允许手动建立骨骼
import {Mat4, Quat, toRadian, Vec3} from "../../../src/math";
import {Camera} from "./Camera";
import {App} from "./App";
import {warnLog} from "../../../src/utils";
const Debug = true;
const tmpM4 = new Mat4();
const tmpV3 = new Vec3();

export class Bone{

    _offsetMatrix:Mat4 = new Mat4();

    _parent:Bone | null = null;
    _children:Bone[] = [];

    // 世界坐标系中
    _worldMatrix:Mat4 = new Mat4();

    // pos 是定义在父骨骼坐标系中的
    public constructor(public localPos:Vec3,public localQuat:Quat) {
    }

    add(bone:Bone){
        if (Debug){
            if (this._children.indexOf(bone) >= 0){
                throw new Error('bone already in children!');
            }

            if (bone._parent){
                throw new Error('bone already has parent');
            }
        }
        this._children.push(bone);
        bone._parent = this;
    }

    updateBoneMatrix(parentWorldMatrix:Mat4){
        // 先更新自己,再更新子节点
        let localMatrix = Mat4.fromRT(tmpM4,this.localQuat,this.localPos);
        Mat4.multiply(this._worldMatrix,parentWorldMatrix,localMatrix);
        this._children.forEach(child=>child.updateBoneMatrix(this._worldMatrix));
    }

    get vertexPos(){
        return Mat4.getTranslation(tmpV3,this._worldMatrix);
    }

    rotateZ(degress:number){
        Quat.rotateZ(this.localQuat,this.localQuat,toRadian(degress));
    }
}


// Mesh的顶点
export class SkinVertex{

    // 一个顶点最多受4个顶点影响
    static MaxBone = 4;

    // blend vertex pos, in world space
    _worldPos = new Vec3();

    _bones : Bone[] = [];
    _weights: number[] = [];

    /**
     *
     * @param _localPos pos in mesh space
     */
    public constructor(public _localPos:Vec3) {
    }

    addAffectBone(bone:Bone,weight:number){
        if (this._bones.length >= SkinVertex.MaxBone){
            warnLog('一个顶点最多只受4个骨骼影响');
            return
        }
        this._bones.push(bone);
        this._weights.push(weight);
    }

    _computeWorldPosByBone(bone:Bone){
        // 1. transform vertex from mesh space to bone space
        let posInBone = Vec3.transformMat4(tmpV3,this._localPos,bone._offsetMatrix);

        // 2. transform vertex from bone space to world space
        return Vec3.transformMat4(posInBone,posInBone,bone._worldMatrix);
    }

    //
    blendVertex(){
        this._worldPos.set(0,0,0);
        this._bones.forEach((bone,idx)=>{
            let weight = this._weights[idx];
            let posInWorld = this._computeWorldPosByBone(bone);
            posInWorld.multiplyScalar(weight);
            this._worldPos.add(posInWorld);
        });
    }
}

export class SkinMesh{
    _typedArr:Float32Array;
    _vao:WebGLVertexArrayObject|null = null;
    _vbo:WebGLBuffer|null = null;
    public constructor(public lstSkinVertex:SkinVertex[]) {
        this._typedArr = new Float32Array(lstSkinVertex.length * 3);
    }

    render(gl:WebGL2RenderingContext,camera:Camera){

        this.lstSkinVertex.forEach((skinVertex,idx)=>{
            skinVertex.blendVertex()
            this._typedArr[3 * idx] = skinVertex._worldPos.x;
            this._typedArr[3 * idx + 1] = skinVertex._worldPos.y;
            this._typedArr[3 * idx + 2] = skinVertex._worldPos.z;
        });

        let shader = App.instance().shaderCache.get('line');
        shader.bind();

        if (!this._vao){
            this._vao = gl.createVertexArray() as WebGLVertexArrayObject
        }
        gl.bindVertexArray(this._vao);
        if (!this._vbo){
            this._vbo = gl.createBuffer();
        }
        gl.bindBuffer(gl.ARRAY_BUFFER,this._vbo);
        gl.bufferData(gl.ARRAY_BUFFER,this._typedArr,gl.DYNAMIC_DRAW);
        let posHandler=shader.attribs['a_position'];
        gl.enableVertexAttribArray(posHandler);
        gl.vertexAttribPointer(posHandler,3,gl.FLOAT,false,0,0);
        shader.setMat4('u_projMatrix',camera.projMat);
        shader.setMat4('u_vmMatrix',camera.viewMat);
        gl.drawArrays(gl.LINE_LOOP,0,this._typedArr.length/3);
        gl.bindVertexArray(null);
        shader.unbind();
    }
}
